package com.fz.springboot.starter;


import cn.hutool.core.exceptions.ExceptionUtil;
import com.fz.springboot.starter.core.exception.BizExceptionVerb.BizException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import static cn.hutool.core.util.ObjectUtil.defaultIfBlank;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.joining;

/**
 * controller advice
 * handle all mvc exception
 *
 * @author fengbinbin
 * @version 1.0
 * @since 3/23/2022 10:10 AM
 */
@Slf4j
@RestControllerAdvice
@ConditionalOnWebApplication
public class WebMvcAdvisor {

    /**
     * business exception
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(BizException.class)
    public Object handleBizException(BizException exception) {
        String bizExceptionMessage = ExceptionUtil.getRootCauseMessage(exception);
        log.error("business exception occurred: {}", defaultIfBlank(bizExceptionMessage, "business exception"));
        return R.fail(bizExceptionMessage);
    }

    /**
     * argument bind exception
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Object handleBindException(BindException exception) {
        String exceptionMessage = exception.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(joining(";"));
        log.error("bind exception occurred: {}", defaultIfBlank(exception.getMessage(), "request argument bind exception"));
        return R.bad(exceptionMessage);
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageConversionException.class)
    public Object convertException(HttpMessageConversionException exception) {
        String convertExceptionMessage = ExceptionUtil.getRootCauseMessage(exception);
        log.error("http message convert exception occurred: {}", defaultIfBlank(convertExceptionMessage, "http message convert exception"));
        return R.bad(convertExceptionMessage);
    }

    /**
     * {@link RequestParam } MissingServletRequestParameterException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Object handleMissingServletRequestParameterException(MissingServletRequestParameterException exception) {
        log.error("missing servlet request parameter exception occurred: {}", defaultIfBlank(exception.getMessage(), "missing servlet request parameter exception"));
        return R.bad(exception.getMessage());
    }

    /**
     * method arguments validate exception ConstraintViolationException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public Object handleConstraintViolationException(ConstraintViolationException exception) {
        String exceptionMessage = exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(joining(";"));
        log.error("constraint violation exception occurred: {}", defaultIfBlank(exception.getMessage(), "constraint violation exception"));
        return R.bad(exceptionMessage);
    }

    /**
     * handle jsr303 exception MethodArgumentNotValidException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
        FieldError fieldError = exception.getBindingResult().getFieldError();
        String     message    = isNull(fieldError) ? erroredMethodMessage(exception) : fieldError.getDefaultMessage();
        log.error("argument not valid exception occurred: {}", defaultIfBlank(exception.getMessage(), "handle method argument not valid exception"));
        return R.bad(message);
    }

    /**
     * HttpRequestMethodNotSupportedException
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Object handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) {
        log.error("http request method not supported exception occurred: {}", defaultIfBlank(exception.getMessage(), "handle http request method not supported exception"));
        return R.bad(exception.getMessage());
    }

    /**
     * RuntimeException
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(RuntimeException.class)
    public Object handleRuntimeException(RuntimeException exception) {
        log.error("exception occurred: ", exception);
        return R.fail(defaultIfBlank(exception.getMessage(), "system error..."));
    }

    //************************************************ private start *************************************************//

    private String erroredMethodMessage(MethodArgumentNotValidException exception) {
        return "method [" + exception.getParameter().getMethod() + "] argument not-validate error";
    }
}
